/*
 * cam.c
 *
 *  Created on: 2020-10-8
 *      Author: h3
 */

#include "cam.h"

/* 函数的实现 */
int cam_open(char *filename)
{
	if (filename != NULL) cam_obj.dev_name = filename;

	struct stat st;
	if (stat(cam_obj.dev_name, &st) < 0) {  //获取文件属性
		perror("get %s stat:");
		return -1;
	}
	if (!S_ISCHR(st.st_mode)) {		//是否是字符设备
		printf("%s is no device\n", cam_obj.dev_name);
		return -1;
	}
	if ((cam_obj.fd = open(cam_obj.dev_name, O_RDWR, 0)) < 0) {		//读写方式打开文件
		perror("open:");
		return -1;
	}

	printf("Open: %s successed!\n", cam_obj.dev_name);
	return 0;
}

int cam_close(void)
{
	if (cam_obj.fd > 0) close(cam_obj.fd);
	printf("Close: successed!\n");
	return 0;
}

int cam_query_capability(void)
{
	struct v4l2_capability cap;
	memset(&cap, 0, sizeof(cap)); //变量清零

	if (ioctl(cam_obj.fd, VIDIOC_QUERYCAP, &cap) < 0) { //查询性能
		perror("query cap:");
		return -1;
	}
	else {
		printf("Query Capability:");
		printf("\tdriver:\t\t%s\n", cap.driver);
		printf("\tcard:\t\t%s\n", cap.card);
		printf("\tbus_info:\t%s\n", cap.bus_info);
		printf("\tversion:\t%d\n", cap.version);
		printf("\tcapabilities:\t%x\n", cap.capabilities);

		if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
			printf("%s is no video capture device\n", cam_obj.dev_name);
		}
		else {
			printf("\t%s is a video capture device\n", cam_obj.dev_name);
		}
		if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
			printf("\tIt can not read\n");
		}
		else {
			printf("\tIt can read\n");
		}
		if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
			printf("It can not streaming\n");
		}
		else {
			printf("\tIt can streaming\n");
		}
	}
	return 0;
}

int cam_enum_std(void)
{
    struct v4l2_standard std;
    memset(&std, 0, sizeof(std));

    std.index = 0;
    printf("Support standard:\n");
    while (ioctl(cam_obj.fd, VIDIOC_ENUMSTD, &std) != -1) {
        printf("\t%d %s\n", std.index + 1, std.name);
        std.index++;
    }

    v4l2_std_id std_id;
    if (ioctl(cam_obj.fd, VIDIOC_G_STD, &std_id) < 0) {
        perror("G_STD:");
        return -1;
    }
    memset(&std, 0, sizeof(std));
    std.index = 0;
    while (0 == ioctl(cam_obj.fd, VIDIOC_ENUMSTD, &std)) {
        if (std.id & std_id) {
            printf("\tCurrent video standard: %s\n", std.name);
            break;
        }
        std.index++;
    }

    return 0;
}

int cam_enum_format(void) //列出设备支持的格式
{
    struct v4l2_fmtdesc fmtdesc;
    memset(&fmtdesc, 0, sizeof(fmtdesc));

    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("Support format:\n");
    while (ioctl(cam_obj.fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
        printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
        fmtdesc.index++;
    }
    return 0;
}

int cam_get_cur_format(void)
{
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (ioctl(cam_obj.fd, VIDIOC_G_FMT, &fmt) < 0) {
        perror("G_FMT");
        return -1;
    }
    {
        printf("Current fmt :\n");
        printf("\tfmt.type:\t\t%d\n", fmt.type);
        printf("\tpix.pixelformat:\t%c%c%c%c\n", fmt.fmt.pix.pixelformat & 0xFF,
                (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
                (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
                (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
        printf("\tpix.height:\t\t%d\n", fmt.fmt.pix.height);
        printf("\tpix.width:\t\t%d\n", fmt.fmt.pix.width);
        printf("\tpix.field:\t\t%d\n", fmt.fmt.pix.field);
    }

    cam_obj.v4l2_pixfmt = fmt.fmt.pix.pixelformat;
    cam_obj.width = fmt.fmt.pix.width;
    cam_obj.height = fmt.fmt.pix.height;
    cam_obj.bytesperline = fmt.fmt.pix.bytesperline;

    return 0;
}

int cam_set_format(__u32 pixfmt, int w, int h)
{
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = pixfmt; // 采样类型，如 RGB 8:8:8
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    fmt.fmt.pix.width = w;
    fmt.fmt.pix.height = h;
    printf("Set format:\n\tset format to be V4L2_PIX_FMT_RGB888\n");
    if (ioctl(cam_obj.fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("S_FMT:");
        return -1;
    }
    return 0;
}

int cam_set_parm(int fps)
{
    struct v4l2_streamparm parm;
    memset(&parm, 0, sizeof(parm));

    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    parm.parm.capture.timeperframe.numerator = 1;
    parm.parm.capture.timeperframe.denominator = fps;
    parm.parm.capture.capturemode = 0;
    if (ioctl(cam_obj.fd, VIDIOC_S_PARM, &parm) < 0) {
        perror("S_PARM");
        //return -1;
    }
    printf("####set streamparm:\n"
                "\tnumerator =%d\n"
                "\tdenominator=%d\n"
                "\tcapturemode=%d\n\n", parm.parm.capture.timeperframe.numerator,
                parm.parm.capture.timeperframe.denominator,
                parm.parm.capture.capturemode);

    return 0;
}

int cam_get_parm(void)
{
    struct v4l2_streamparm parm;
    memset(&parm, 0, sizeof(parm));

    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(cam_obj.fd, VIDIOC_G_PARM, &parm) < 0) {
        perror("G_PARM:");
        return -1;
    }
    printf("Get streamparm:\n"
            "\tnumerator =%d\n"
            "\tdenominator=%d\n"
            "\tcapturemode=%d\n\n", parm.parm.capture.timeperframe.numerator,
            parm.parm.capture.timeperframe.denominator,
            parm.parm.capture.capturemode);

    cam_obj.fps = parm.parm.capture.timeperframe.denominator/
                parm.parm.capture.timeperframe.numerator;
    return 0;
}

int cam_req_bufs(int count)
{
    struct v4l2_requestbuffers reqbuf;
    memset(&reqbuf, 0, sizeof(reqbuf));

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    reqbuf.count = count;
    if (ioctl(cam_obj.fd, VIDIOC_REQBUFS, &reqbuf) < 0) { //VIDIOC_REQBUFS：分配内存
        perror("VIDIOC_REQBUFS");
        return -1;
    }
    printf("\nSuccess in VIDEO_REQBUFS\n");
    if (reqbuf.count < 1) {
        printf("\tNot enough buffer memory\n");
        return -1;
    }
    cam_obj.nr_buffers = reqbuf.count;
    printf("\tRaw buffer count is %d\n", cam_obj.nr_buffers);
    return 0;
}

int cam_mmap(void)
{
    cam_obj.buffers = (cam_buffer_t *)calloc(cam_obj.nr_buffers, sizeof(cam_buffer_t));

    struct v4l2_buffer bufinfo;
    for (int i = 0; i < cam_obj.nr_buffers; i++) {
        memset(&bufinfo, 0, sizeof(bufinfo));
        bufinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        bufinfo.memory = V4L2_MEMORY_MMAP;
        bufinfo.index = i;

        if (ioctl(cam_obj.fd, VIDIOC_QUERYBUF, &bufinfo) < 0) {
            perror("QUERYBUFS");
            return -1;
        }
        else
            printf("Success QUERYBUFS\n");

        cam_obj.buffers[i].length = bufinfo.length;
        cam_obj.buffers[i].start = mmap(NULL, bufinfo.length, PROT_READ | PROT_WRITE,
        MAP_SHARED, cam_obj.fd, bufinfo.m.offset);
        if (cam_obj.buffers[i].start == MAP_FAILED) {
            perror("MMAP");
            return -1;
        }
    }
    return 0;
}

int cam_munmap(void)
{
    for (int i = 0; i < cam_obj.nr_buffers; i++) {
        if (munmap(cam_obj.buffers[i].start, cam_obj.buffers[i].length) < 0) {
            perror("munmap");
            return -1;
        }
    }
    free(cam_obj.buffers);
    return 0;
}

int cam_start(void)
{
    for (int i = 0; i < cam_obj.nr_buffers; i++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        if (ioctl(cam_obj.fd, VIDIOC_QBUF, &buf) < 0) {
            perror("QBUF");
            return -1;
        }
    }
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(cam_obj.fd, VIDIOC_STREAMON, &type) < 0) {
        perror("STREAMON");
        return -1;
    }

    return 0;
}

int cam_stop(void)
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(cam_obj.fd, VIDIOC_STREAMOFF, &type) < 0) {
        perror("STREAMOFF");
        return -1;
    }
    return 0;
}

int cam_get_frame(unsigned char **_buf)
{
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    if (ioctl(cam_obj.fd, VIDIOC_DQBUF, &buf) < 0) {
        perror("DQBUFS");
        return -1;
    }

    cam_obj.cur_buffer_index = buf.index;

    *_buf = cam_obj.buffers[buf.index].start;
    return 0;
}

int cam_free_frame(void)
{
    if (cam_obj.cur_buffer_index == -1) return 0;

    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = cam_obj.cur_buffer_index;

    if (ioctl(cam_obj.fd, VIDIOC_QBUF, &buf) < 0) {
        perror("QBUF");
        return -1;
    }

    return 0;
}


int cam_init(char *filename,__u32 pixfmt,int w,int h,int fps,int bufcount)
{
	cam_obj.open(filename);
	cam_obj.query_capability();
	cam_obj.enum_std();
	cam_obj.enum_format();
	cam_obj.set_format(pixfmt, w, h);
	cam_obj.get_cur_format();
	cam_obj.set_parm(fps);
	cam_obj.get_parm();
	cam_obj.req_bufs(bufcount);
	cam_obj.mmap();
	return 0;
}

int cam_release(void)
{
	cam_obj.stop();
	cam_obj.munmap();
	cam_obj.close();

	return 0;
}

/* 模块对象的定义 */
cam_t cam_obj = {
	.dev_name = "/dev/video0",
	.fd = -1,
	.open = cam_open,
	.close = cam_close,
	.query_capability = cam_query_capability,
	.enum_std = cam_enum_std,
	.enum_format = cam_enum_format,
	.get_cur_format = cam_get_cur_format,
	.set_format = cam_set_format,
	.get_parm = cam_get_parm,
	.set_parm = cam_set_parm,
	.req_bufs = cam_req_bufs,
	.mmap = cam_mmap,
	.munmap = cam_munmap,
	.start = cam_start,
	.stop = cam_stop,
	.get_frame = cam_get_frame,
	.free_frame = cam_free_frame,
	.init = cam_init,
	.release = cam_release,
};
